Skip to content

feat(claude): stabilize device fingerprint across mixed Claude Code and cloaked clients#2213

Open
TTTPOB wants to merge 9 commits intorouter-for-me:mainfrom
TTTPOB:ua-fix
Open

feat(claude): stabilize device fingerprint across mixed Claude Code and cloaked clients#2213
TTTPOB wants to merge 9 commits intorouter-for-me:mainfrom
TTTPOB:ua-fix

Conversation

@TTTPOB
Copy link

@TTTPOB TTTPOB commented Mar 18, 2026

Summary

This draft PR adds an optional Claude device-profile stabilization feature.

When enabled, CLIProxyAPI learns and pins a single outgoing fingerprint per upstream identity, so mixed traffic from:

  • real Claude Code clients
  • cloaked non-Claude clients

does not cause User-Agent / X-Stainless-* downgrade flicker at Anthropic.

Motivation

Directly related external report:

That issue describes the same risk: one real Claude Code client upgrades the observed fingerprint, then a later cloaked request downgrades it back to an older/default one. The same upstream identity then appears to jump between client versions/environments, which may increase ban risk.

Related Upstream Work

This builds on earlier Claude cloaking / fingerprint work in this repo:

  • #868 native Claude cloaking
  • #1621 discussion of applyClaudeHeaders() defaults
  • #1628 configurable Claude header defaults
  • #1662 cache-user-id toggle
  • #1750 alignment with real Claude Code 2.1.63

What This PR Does

  • Treats these fields as one device-profile bundle:
    • User-Agent
    • X-Stainless-Package-Version
    • X-Stainless-Runtime-Version
    • X-Stainless-Os
    • X-Stainless-Arch
  • Caches that profile per upstream identity
    • prefer auth.ID
    • otherwise API-key scope
  • Updates the cached profile only when a higher claude-cli/<semver> version is observed
  • Reuses that resolved profile for all outgoing Anthropic requests when enabled
  • Extends claude-header-defaults with:
    • os
    • arch
    • stabilize-device-profile

Config

claude-header-defaults:
  user-agent: "claude-cli/2.1.63 (external, cli)"
  package-version: "0.74.0"
  runtime-version: "v24.3.0"
  os: "MacOS"
  arch: "arm64"
  timeout: "600"
  stabilize-device-profile: false

stabilize-device-profile defaults to false.

This is intentional: some personal/shared deployments may accept the risk of mixed fingerprints if all users are trusted. Operators must opt in explicitly to enable profile pinning.

Important Notes

  • Cold start:
    if no official Claude fingerprint has been observed yet for a scope, the configured baseline is used.
  • Same-version CLI / extension:
    if two official-looking clients report the same claude-cli/<semver> version, first observed wins. Equal-version replacements are ignored to avoid environment flicker.
  • Session semantics:
    this does not change the separation between device fingerprint and metadata.user_id / session behavior.

Security Concern

This draft exists because fingerprint learning itself has trust implications.

If a client sends:

  • a non-existent Claude Code version
  • an invalid combination of User-Agent and X-Stainless-*
  • a semantically unrealistic profile

then pinning that profile could itself increase ban risk.

Two possible hardening directions:

  1. require admin confirmation before upgrading to a newly observed version
  2. maintain a whitelist of allowed Claude versions / combinations

Persistence Concern

This implementation is currently in-memory only.

It does not persist learned device profiles across process restarts yet. That is partly because the trust policy is still unresolved: persisting an unverified learned fingerprint would make a bad profile survive restarts.

There is existing per-account persistence infrastructure for file-backed/OAuth auth records, but not a single ready-made persistence path for both:

  • auth-file/OAuth identities
  • config-only API-key identities

So persistence is better handled in a follow-up after the trust model is settled.

Tests

Added coverage for:

  • baseline fingerprint on cold start
  • upgrade-only profile replacement
  • no downgrade on lower versions
  • disabled mode fallback
  • config loading for os, arch, and stabilize-device-profile

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces an optional feature to stabilize the Claude device fingerprint for outgoing Anthropic API requests. By learning and pinning a single, consistent device profile per upstream identity, it aims to prevent 'fingerprint flicker' caused by mixed traffic from genuine Claude Code clients and cloaked non-Claude clients. This stabilization helps reduce the risk of upstream bans by presenting a consistent client identity to Anthropic, addressing a known issue where fluctuating User-Agent and X-Stainless-* headers could flag an account for suspicious activity.

Highlights

  • Device Profile Bundling: Grouped User-Agent, X-Stainless-Package-Version, X-Stainless-Runtime-Version, X-Stainless-Os, and X-Stainless-Arch into a single device-profile bundle for consistent identification.
  • Per-Identity Profile Caching: Implemented caching of the device profile per upstream identity, preferring auth.ID or falling back to API-key scope, to maintain a stable fingerprint.
  • Version-Based Profile Updates: Configured the cached profile to update only when a higher claude-cli/<semver> version is observed, preventing downgrades and ensuring the most up-to-date profile is used.
  • Configurable Stabilization: Introduced a new stabilize-device-profile option in claude-header-defaults (defaulting to false) to allow operators to explicitly opt-in to device profile pinning.
  • Integration into Header Application: Integrated the new device profile resolution and application logic into the applyClaudeHeaders function, conditionally applying stabilized headers or falling back to legacy behavior.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The pull request introduces a new feature for stabilizing Claude device fingerprints, which is well-implemented with clear separation of concerns and good test coverage. The changes correctly integrate new configuration options, update the header application logic, and provide robust tests for various scenarios, including baseline usage, version upgrades, and disabled mode. The new claude_device_profile.go file encapsulates the core logic effectively, and the config.go updates ensure proper handling of the new settings. Overall, the code maintains a high standard of quality and addresses the stated motivation effectively.

@TTTPOB TTTPOB marked this pull request as ready for review March 18, 2026 13:52
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0e337aeb9

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +101 to +102
OS: hdrDefault(hd.OS, defaultClaudeFingerprintOS),
Arch: hdrDefault(hd.Arch, defaultClaudeFingerprintArch),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep legacy OS/arch fallback runtime-derived

When stabilize-device-profile is unset or false, the legacy path still calls defaultClaudeDeviceProfile(), so non-Claude clients that omit X-Stainless-Os/X-Stainless-Arch now advertise the hard-coded MacOS/arm64 pair from here. Before this patch applyClaudeHeaders() used mapStainlessOS()/mapStainlessArch(), so Linux and Windows deployments will silently change their outgoing fingerprint even though the new feature is supposed to be opt-in.

Useful? React with 👍 / 👎.

Comment on lines +284 to +286
if isClaudeCodeClient(clientUA) {
r.Header.Set("User-Agent", clientUA)
return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve configured User-Agent overrides for real Claude clients

This branch now runs after util.ApplyCustomHeadersFromAttrs() and unconditionally rewrites User-Agent back to the incoming claude-cli/... value whenever the caller is Claude Code. That means a claude-api-key[].headers.User-Agent override no longer works for official clients when stabilization is disabled, even though per-key headers used to win before this refactor; any upstream routing or allowlisting that depends on the configured UA will stop working for those requests.

Useful? React with 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dd64adbeeb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +244 to +250
if hasCandidate && (!cachedValid || candidate.Version.Compare(entry.profile.Version) > 0) {
newEntry := claudeDeviceProfileCacheEntry{
profile: candidate,
expire: now.Add(claudeDeviceProfileTTL),
}
claudeDeviceProfileCacheMu.Lock()
claudeDeviceProfileCache[cacheKey] = newEntry

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-check cached version under the write lock

resolveClaudeDeviceProfile decides whether to accept candidate from a read-lock snapshot and then writes it unconditionally after taking the write lock. If two requests for the same auth race on a cold cache (for example claude-cli/2.1.63 and claude-cli/2.1.62), both will see cachedValid == false, and the lower-version goroutine can arrive second and overwrite the newer profile. That makes the stabilized fingerprint nondeterministic under normal concurrent load and reintroduces the downgrade flicker this feature is supposed to prevent.

Useful? React with 👍 / 👎.

Comment on lines +236 to +245
baseline := defaultClaudeDeviceProfile(cfg)
candidate, hasCandidate := extractClaudeDeviceProfile(headers, cfg)

claudeDeviceProfileCacheMu.RLock()
entry, hasCached := claudeDeviceProfileCache[cacheKey]
cachedValid := hasCached && entry.expire.After(now) && entry.profile.UserAgent != ""
claudeDeviceProfileCacheMu.RUnlock()

if hasCandidate && (!cachedValid || candidate.Version.Compare(entry.profile.Version) > 0) {
newEntry := claudeDeviceProfileCacheEntry{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Compare first learned profile against the configured baseline

On a cold start, this branch stores any official-looking Claude client profile without checking whether it is older than the configured claude-header-defaults baseline. In a deployment that pins a newer baseline (for cloaked traffic) and still has some older Claude Code clients, the first older client request will downgrade the cached fingerprint from the configured version to its older claude-cli/... tuple, even though the new feature describes the config values as the seeded pinned baseline.

Useful? React with 👍 / 👎.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8179d5a8a4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +865 to +866
if stabilizeDeviceProfile {
applyClaudeDeviceProfileHeaders(r, deviceProfile)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve custom fingerprint headers when stabilization is enabled

When stabilize-device-profile is true and an auth entry sets header:User-Agent or header:X-Stainless-*, util.ApplyCustomHeadersFromAttrs() writes those values first but this branch immediately overwrites all five fingerprint headers from deviceProfile. internal/util/header_helpers.go explicitly documents custom headers as overriding built-in defaults, so enabling stabilization now silently breaks per-key routing or allowlisting setups that pin a specific Claude fingerprint via claude-api-key[].headers.

Useful? React with 👍 / 👎.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in fact I don't know how should this feature be designed. it must be one overriding another if both configs are enabled.

Comment on lines +252 to +254
candidate, hasCandidate := extractClaudeDeviceProfile(headers, cfg)
if hasCandidate && !shouldUpgradeClaudeDeviceProfile(candidate, baseline) {
hasCandidate = false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let the first same-version Claude client replace the seeded baseline

On a cold cache, a real Claude client whose claude-cli/<semver> exactly matches the configured baseline is discarded here because shouldUpgradeClaudeDeviceProfile(candidate, baseline) only accepts strictly newer versions. In the common case where operators seed the current Claude release but the first real client runs a different OS/arch or package/runtime, the proxy never learns that official profile and keeps advertising the synthetic baseline until some later version appears.

Useful? React with 👍 / 👎.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since admin is the one who will be responsible for configuring a baseline, it would not be a problem.

@TTTPOB
Copy link
Author

TTTPOB commented Mar 19, 2026

Addressed the stabilized Claude fingerprint OS/arch flicker issue in commit f7069e95.

What changed:

  • X-Stainless-Os / X-Stainless-Arch are now always pinned to the claude-header-defaults baseline when stabilize-device-profile is enabled.
  • User-Agent / X-Stainless-Package-Version / X-Stainless-Runtime-Version can still upgrade when a newer official Claude client is observed.
  • Added regression coverage for the cold-start path where a third-party request hits first and an official client arrives later, plus the concurrent upgrade path.

Validated with:

  • GOCACHE=/tmp/go-build go test ./internal/runtime/executor -run 'Test(ApplyClaudeHeaders_(UsesConfiguredBaselineFingerprint|TracksHighestClaudeCLIFingerprint|DoesNotDowngradeConfiguredBaselineOnFirstClaudeClient|ThirdPartyBaselineThenOfficialUpgradeKeepsPinnedPlatform|DisableDeviceProfileStabilization|LegacyModePreservesConfiguredUserAgentOverrideForClaudeClients|LegacyModeFallsBackToRuntimeOSArchWhenMissing|UnsetStabilizationAlsoUsesLegacyRuntimeOSArchFallback)|ResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate)$'
  • GOCACHE=/tmp/go-build go test ./internal/config -run 'Test.*Claude.*'

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f7069e9548

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +262 to +263
if hasCandidate && !shouldUpgradeClaudeDeviceProfile(candidate, baseline) {
hasCandidate = false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow learning when the configured baseline UA is custom

If an operator turns on stabilize-device-profile in an existing config that already overrides claude-header-defaults.user-agent to a non-claude-cli/... value, defaultClaudeDeviceProfile() leaves baseline.Version unset and this guard drops every real Claude candidate. In that scenario resolveClaudeDeviceProfile will keep returning the custom baseline forever, so the new stabilization feature never learns the actual fingerprint for that auth even after official Claude Code traffic arrives.

Useful? React with 👍 / 👎.

Comment on lines +301 to +303
entry.profile = pinClaudeDeviceProfilePlatform(entry.profile, baseline)
entry.expire = now.Add(claudeDeviceProfileTTL)
claudeDeviceProfileCache[cacheKey] = entry

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate cached software fingerprints after config hot reload

The service hot-reloads config (sdk/cliproxy/service.go rebinds executors on reload), but this cached-return path only re-pins OS/Arch and extends the TTL. If an operator raises claude-header-defaults.user-agent / package / runtime at runtime, any auth that already learned an older profile will keep sending the stale software fingerprint for up to 7 days because the package-global cache is never cleared or compared against the new baseline.

Useful? React with 👍 / 👎.

@TTTPOB
Copy link
Author

TTTPOB commented Mar 19, 2026

Addressed the baseline-upgrade cache-renewal issue in commit 680105f8.

What changed:

  • Cached stabilized profiles are now normalized against the current claude-header-defaults baseline before renewal.
  • That normalization still pins X-Stainless-Os / X-Stainless-Arch to the baseline.
  • It now also treats the current baseline software fingerprint (User-Agent / X-Stainless-Package-Version / X-Stainless-Runtime-Version) as a floor, so a hot-reloaded higher baseline immediately upgrades active cached profiles on the next request instead of renewing stale software fields.
  • Added a regression test that warms the cache under an older baseline, then reuses the same auth under a newer baseline with only third-party traffic and verifies the cached fingerprint upgrades immediately.

Validated with:

  • GOCACHE=/tmp/go-build go test ./internal/runtime/executor -run 'Test(ApplyClaudeHeaders_(UsesConfiguredBaselineFingerprint|TracksHighestClaudeCLIFingerprint|DoesNotDowngradeConfiguredBaselineOnFirstClaudeClient|UpgradesCachedSoftwareFingerprintWhenBaselineAdvances|ThirdPartyBaselineThenOfficialUpgradeKeepsPinnedPlatform|DisableDeviceProfileStabilization|LegacyModePreservesConfiguredUserAgentOverrideForClaudeClients|LegacyModeFallsBackToRuntimeOSArchWhenMissing|UnsetStabilizationAlsoUsesLegacyRuntimeOSArchFallback)|ResolveClaudeDeviceProfile_RechecksCacheBeforeStoringCandidate)$'

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 680105f84d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +230 to +233
case auth != nil && strings.TrimSpace(auth.ID) != "":
return "auth:" + strings.TrimSpace(auth.ID)
case strings.TrimSpace(apiKey) != "":
return "api_key:" + strings.TrimSpace(apiKey)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scope stabilized profiles by upstream key, not auth ID

When the same Anthropic credential is exposed through multiple auth records, this buckets the cache by auth.ID and never falls back to the actual api_key. The synthesizer intentionally gives duplicate claude-api-key entries distinct IDs (internal/watcher/synthesizer/helpers.go:45-51), so two prefixes/model pools backed by one upstream key will learn separate fingerprints here. Anthropic still sees one API key, so mixed traffic across those routes will continue to flip the outgoing fingerprint and defeat the stabilization feature in a common shared-key setup.

Useful? React with 👍 / 👎.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if admin intentionally separate one oauth/apikey to multiple auth id, we should just respect the separation

Comment on lines +200 to +203
userAgent := strings.TrimSpace(headers.Get("User-Agent"))
version, ok := parseClaudeCLIVersion(userAgent)
if !ok {
return claudeDeviceProfile{}, false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject spoofed claude-cli headers before caching them

This accepts any request whose User-Agent merely parses as claude-cli/<semver> as an official candidate, with no further validation before resolveClaudeDeviceProfile stores it for 7 days. In an opt-in deployment that serves untrusted or partially cloaked clients, one spoofed header like claude-cli/999.0.0 plus made-up X-Stainless-* values will permanently poison the cached fingerprint for that auth scope, which is the opposite of what this feature is trying to protect against.

Useful? React with 👍 / 👎.

@TTTPOB
Copy link
Author

TTTPOB commented Mar 19, 2026

Addressed this in commit 52c1fa02.

The change makes stabilized profiles learn the official Claude software fingerprint even when claude-header-defaults.user-agent is configured to a non-claude-cli/... value.

Our reasoning here is pragmatic: deliberately configuring a non-official baseline UA while also enabling stabilize-device-profile is a rare situation. In that case we think it is acceptable to treat the custom baseline as a cold-start fallback only, and then override it once real official Claude Code traffic is observed.

Behavior after this fix:

  • third-party traffic still uses the custom baseline as the initial fallback;
  • the first real Claude Code request immediately establishes the learned official User-Agent / package-version / runtime-version for that auth;
  • later third-party traffic keeps using that learned official software fingerprint instead of falling back to the custom baseline again;
  • OS/Arch still remain pinned to the configured baseline.

Validated with the stabilized fingerprint executor regression suite, including a new test covering custom baseline fallback -> official learning -> third-party reuse.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52c1fa025e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +105 to +106
OS: hdrDefault(hd.OS, defaultClaudeFingerprintOS),
Arch: hdrDefault(hd.Arch, defaultClaudeFingerprintArch),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid defaulting unstated stabilized platform to MacOS/arm64

If an existing deployment enables claude-header-defaults.stabilize-device-profile without also adding the new os/arch keys, defaultClaudeDeviceProfile() seeds the baseline with the hard-coded MacOS/arm64 pair here, and pinClaudeDeviceProfilePlatform() then rewrites every learned profile to that platform. In practice, Linux/Windows installs that opt into stabilization will immediately start advertising a synthetic Mac fingerprint even for genuine Claude Code traffic, which is a silent behavior change many operators will hit because older configs do not contain these new fields.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant